/*++

Copyright (c) Microsoft Corporation.  All rights reserved.

    THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
    PURPOSE.

Module Name:

    IsrDpc.c

Abstract:

    Contains routines related to interrupt and dpc handling.

Environment:

    Kernel mode

--*/

#include "precomp.h"

#include "IsrDpc.tmh"

NTSTATUS
PLxInterruptCreate(
    IN PDEVICE_EXTENSION DevExt
    )
/*++
Routine Description:

    Configure and create the WDFINTERRUPT object.
    This routine is called by EvtDeviceAdd callback.

Arguments:

    DevExt      Pointer to our DEVICE_EXTENSION

Return Value:

    NTSTATUS code

--*/
{
    NTSTATUS                    status;
    WDF_INTERRUPT_CONFIG        InterruptConfig;

    WDF_INTERRUPT_CONFIG_INIT( &InterruptConfig,
                               PLxEvtInterruptIsr,
                               PLxEvtInterruptDpc );

    InterruptConfig.EvtInterruptEnable  = PLxEvtInterruptEnable;
    InterruptConfig.EvtInterruptDisable = PLxEvtInterruptDisable;

    // JOHNR: Enable testing of the DpcForIsr Synchronization
    InterruptConfig.AutomaticSerialization = TRUE;

    //
    // Unlike WDM, framework driver should create interrupt object in EvtDeviceAdd and
    // let the framework do the resource parsing and registration of ISR with the kernel.
    // Framework connects the interrupt after invoking the EvtDeviceD0Entry callback
    // and disconnect before invoking EvtDeviceD0Exit. EvtInterruptEnable is called after
    // the interrupt interrupt is connected and EvtInterruptDisable before the interrupt is
    // disconnected.
    //
    status = WdfInterruptCreate( DevExt->Device,
                                 &InterruptConfig,
                                 WDF_NO_OBJECT_ATTRIBUTES,
                                 &DevExt->Interrupt );

    if( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
                    "WdfInterruptCreate failed: %!STATUS!", status);
    }

    return status;
}

BOOLEAN
PLxEvtInterruptIsr(
    IN WDFINTERRUPT Interrupt,
    IN ULONG        MessageID
    )
/*++
Routine Description:

    Interrupt handler for this driver. Called at DIRQL level when the
    device or another device sharing the same interrupt line asserts
    the interrupt. The driver first checks the device to make sure whether
    this interrupt is generated by its device and if so clear the interrupt
    register to disable further generation of interrupts and queue a
    DPC to do other I/O work related to interrupt - such as reading
    the device memory, starting a DMA transaction, coping it to
    the request buffer and completing the request, etc.

Arguments:

    Interupt   - Handle to WDFINTERRUPT Object for this device.
    MessageID  - MSI message ID (always 0 in this configuration)

Return Value:

     TRUE   --  This device generated the interrupt.
     FALSE  --  This device did not generated this interrupt.

--*/
{
    PDEVICE_EXTENSION   devExt;
    BOOLEAN             isRecognized = FALSE;

    union {
        INT_CSR bits;
        ULONG   ulong;
    } intCsr;

    UNREFERENCED_PARAMETER(MessageID);

    //TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INTERRUPT,
    //            "--> PLxInterruptHandler");

    devExt  = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt));

    //
    // Read the Interrupt CSR register (INTCSR)
    //
    intCsr.ulong = READ_REGISTER_ULONG( (PULONG) &devExt->Regs->Int_Csr );

    //
    // Is DMA channel 0 (Write-side) Active?
    //
    if (intCsr.bits.DmaChan0IntActive) {

        TraceEvents(TRACE_LEVEL_INFORMATION,  DBG_INTERRUPT,
                    " Interrupt for DMA Channel 0 (write)");

        devExt->IntCsr.bits.DmaChan0IntActive = TRUE;

        //
        // Clear this interrupt.
        //
        devExt->Dma0Csr.uchar =
            READ_REGISTER_UCHAR( (PUCHAR) &devExt->Regs->Dma0_Csr );

        devExt->Dma0Csr.bits.Clear = TRUE;

        WRITE_REGISTER_UCHAR( (PUCHAR) &devExt->Regs->Dma0_Csr,
                              devExt->Dma0Csr.uchar );

        isRecognized = TRUE;
    }

    //
    // Is DMA channel 1 (Read-side) Active?
    //
    if (intCsr.bits.DmaChan1IntActive) {

        TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INTERRUPT,
                    " Interrupt for DMA Channel 1 (read)");

        devExt->IntCsr.bits.DmaChan1IntActive = TRUE;

        //
        // Clear this interrupt.
        //
        devExt->Dma1Csr.uchar =
            READ_REGISTER_UCHAR( (PUCHAR) &devExt->Regs->Dma1_Csr );

        devExt->Dma1Csr.bits.Clear = TRUE;

        WRITE_REGISTER_UCHAR( (PUCHAR) &devExt->Regs->Dma1_Csr,
                              devExt->Dma1Csr.uchar );

        isRecognized = TRUE;
    }

    if ((isRecognized) &&
        ((devExt->Dma0Csr.bits.Done) ||
         (devExt->Dma1Csr.bits.Done))) {
        //
        // A read or a write or both is done. Queue a DPC.
        //
        WdfInterruptQueueDpcForIsr( devExt->Interrupt );
    }

    //TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INTERRUPT,
    //            "<-- PLxInterruptHandler");

    return isRecognized;
}

VOID
PLxEvtInterruptDpc(
    IN WDFINTERRUPT Interrupt,
    IN WDFDEVICE    Device
    )
/*++

Routine Description:

    DPC callback for ISR. Please note that on a multiprocessor system,
    you could have more than one DPCs running simulataneously on
    multiple processors. So if you are accesing any global resources
    make sure to synchrnonize the accesses with a spinlock.

Arguments:

    Interupt  - Handle to WDFINTERRUPT Object for this device.
    Device    - WDFDEVICE object passed to InterruptCreate

Return Value:

--*/
{
    NTSTATUS            status;
    WDFDMATRANSACTION   dmaTransaction;
    PDEVICE_EXTENSION   devExt;
    BOOLEAN             writeInterrupt = FALSE;
    BOOLEAN             readInterrupt  = FALSE;

    UNREFERENCED_PARAMETER(Device);


    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_DPC, "--> EvtInterruptDpc");

    devExt  = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt));

    //
    // Acquire this device's InterruptSpinLock.
    //
    WdfInterruptAcquireLock( Interrupt );


    if ((devExt->IntCsr.bits.DmaChan0IntActive) &&
        (devExt->Dma0Csr.bits.Done)) {

        //
        // If Dma0 channel 0 (write) is interrupting and the
        //  Done bit is set in the Dma0 CSR,
        //  we're interrupting because a WRITE is complete.
        // Clear the done bit and channel interrupting bit from
        //  our copies...
        //
        devExt->IntCsr.bits.DmaChan0IntActive = FALSE;
        devExt->Dma0Csr.uchar = 0;

        writeInterrupt = TRUE;
    }

    if ((devExt->IntCsr.bits.DmaChan1IntActive) &&
        (devExt->Dma1Csr.bits.Done)) {

        //
        // If DMA channel 1 is interrupting and the
        //  DONE bit is set in the DMA1 control/status
        //  register, we're interrupting because a READ
        //  is complete.
        // Clear the done bit and channel interrupting bit from
        //  our copies...
        //
        devExt->IntCsr.bits.DmaChan1IntActive = FALSE;
        devExt->Dma0Csr.uchar = 0;

        readInterrupt = TRUE;
    }

    //
    // Release our interrupt spinlock
    //
    WdfInterruptReleaseLock( Interrupt );

    //
    // Did a Write DMA complete?
    //
    if (writeInterrupt) {

        BOOLEAN transactionComplete;

        //
        // Get the current Write DmaTransaction.
        //
        dmaTransaction = devExt->WriteDmaTransaction;

        //
        // Indicate this DMA operation has completed:
        // This may drive the transfer on the next packet if
        // there is still data to be transfered in the request.
        //
        transactionComplete = WdfDmaTransactionDmaCompleted( dmaTransaction,
                                                         &status );

        if (transactionComplete) {
            //
            // Complete this DmaTransaction.
            //
            TraceEvents(TRACE_LEVEL_INFORMATION, DBG_DPC,
                                     "Completing Write request in the DpcForIsr");

            PLxWriteRequestComplete( dmaTransaction, status );

        }
    }

    //
    // Did a Read DMA complete?
    //
    if (readInterrupt) {

        BOOLEAN                transactionComplete;
        PDMA_TRANSFER_ELEMENT  dteVA;
        size_t                 length;

        //
        // Get the current Read DmaTransaction.
        //
        dmaTransaction = devExt->ReadDmaTransaction;

        //
        // Only on Read-side --
        //    Use "DMA Clear-Count Mode" to get complemetary
        //    transferred byte count.
        //
        length = WdfDmaTransactionGetCurrentDmaTransferLength( dmaTransaction );

        dteVA = (PDMA_TRANSFER_ELEMENT) devExt->ReadCommonBufferBase;

        while(dteVA->DescPtr.LastElement == FALSE) {
            length -= dteVA->TransferSize;
            dteVA++;
        }
        length -= dteVA->TransferSize;

        //
        // Indicate this DMA operation has completed:
        // This may drive the transfer on the next packet if
        // there is still data to be transfered in the request.
        //
        transactionComplete =
            WdfDmaTransactionDmaCompletedWithLength( dmaTransaction,
                                                     length,
                                                     &status );

        if (transactionComplete) {
            //
            // Complete this DmaTransaction.
            //
            TraceEvents(TRACE_LEVEL_INFORMATION, DBG_DPC,
                                    "Completing Read request in the DpcForIsr");

            PLxReadRequestComplete( dmaTransaction, status );

        }
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_DPC, "<-- EvtInterruptDpc");

    return;
}

NTSTATUS
PLxEvtInterruptEnable(
    IN WDFINTERRUPT Interrupt,
    IN WDFDEVICE    Device
    )
/*++

Routine Description:

    Called by the framework at DIRQL immediately after registering the ISR with the kernel
    by calling IoConnectInterrupt.

Return Value:

    NTSTATUS
--*/
{
    PDEVICE_EXTENSION  devExt;

    union {
        INT_CSR   bits;
        ULONG     ulong;
    } intCSR;

    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INTERRUPT,
                "PLxEvtInterruptEnable: Interrupt 0x%p, Device 0x%p\n",
                Interrupt, Device);

    devExt  = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt));

    intCSR.ulong = READ_REGISTER_ULONG( (PULONG) &devExt->Regs->Int_Csr );

    intCSR.bits.PciIntEnable = TRUE;

    WRITE_REGISTER_ULONG( (PULONG) &devExt->Regs->Int_Csr,
                          intCSR.ulong );

    return STATUS_SUCCESS;
}

NTSTATUS
PLxEvtInterruptDisable(
    IN WDFINTERRUPT Interrupt,
    IN WDFDEVICE    Device
    )
/*++

Routine Description:

    Called by the framework at DIRQL before Deregistering the ISR with the kernel
    by calling IoDisconnectInterrupt.

Return Value:

    NTSTATUS
--*/
{
    PDEVICE_EXTENSION  devExt;

    union {
        INT_CSR   bits;
        ULONG     ulong;
    } intCSR;

    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INTERRUPT,
                "PLxEvtInterruptDisable: Interrupt 0x%p, Device 0x%p\n",
                Interrupt, Device);

    devExt  = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt));

    intCSR.ulong = READ_REGISTER_ULONG( (PULONG) &devExt->Regs->Int_Csr );

    intCSR.bits.PciIntEnable = FALSE;

    WRITE_REGISTER_ULONG( (PULONG) &devExt->Regs->Int_Csr,
                          intCSR.ulong );

    return STATUS_SUCCESS;
}

